%% movfun
% Function by adq@XJTU
% 将一维数组 x 划分出滑窗后分别应用函数.
% A = movfun(func, x, nSlide, nOverlap, zPadding, zpAlign)
%
% 单个滑窗的长度指定为 nSlide, 滑窗之间可以有长度为 nOverlap 的重叠.
% 输出 cell 数组 A 为函数句柄对每一个滑窗作用后返回值的顺序拼接.
%
% 首被定义为数组的 1 下标位置, 尾则是最大下标位置. 
% 如果 zPadding 为 true, 则每个滑窗长度都为 nSlide, 并在数组的首/尾滑窗补若干个 0. 否则, 首/尾滑窗长度可能短于 nSlide.
% 如果 zpAlign 为 false / 0 , 滑窗向左(首部)对齐, 在尾端出现可能的补 0 或短滑窗. 此时, 函数先应用于左侧滑窗.
% 如果 zpAlign 为 true / 1, 滑窗向右(尾部)对齐, 在首端出现可能的补 0 或短滑窗. 此时, 函数先应用于右侧滑窗.
% 如果 func 是时变的, 这可能会影响最终结果.
% 
% x 应当为一位数组. 
% 设 x 具有 nData 长度, 则应有 nData >= nSlide > nOverlap >= 0
% 
% History
% 使用 inputParser 进行解析, 对用户更友好.
% 31 Dec. 2021
% 创建并实现基础功能. 使用 assert 手动完成参数验证. 无命令解析, 所有参数必须在对应位置指定.
function A = movfun(func, x, nSlide, nOverlap, varagin)

	zPadding_default = true;
	zpAlign_default = 'left';

	p = inputParser;

	addRequired(p, 'func', @(x) class(x) == 'function_handle');
	addRequired(p, 'x', @(x) isnumeric(x) && isvector(x));
	addRequired(p, 'nSlide',@(x) isnumeric(x) && isscalar(x) && (x > 0));
	addRequired(p, 'nOverlap',@(x) isnumeric(x) && isscalar(x) && (x >= 0));

	addOptional(p, 'zeropadding', zPadding_default, @(x) islogical(x) && isscalar(x));
	addOptional(p, 'align', zpAlign_default, @(x) ischar(x));
	
	parse(p, func, x, nSlide, nOverlap, varagin)

	nData = length(x);
	nAppend = nSlide - nOverlap; % 每次滑窗新增的数据数量

	assert((nData >= nSlide), '数组长度应大于滑窗长度.');
	assert((nAppend > 0 && nOverlap >= 0), '滑窗长度应大于重叠区域长度.');
	
	if (p.Results.zeropadding), zPadding = true, end

	if (p.Results.align(1) == 'r' || p.Results.align(1) == 'R')
		zpAlign = true;
	else
		zpAlign = false;
	end
	
	slide_count = ceil((nData - nSlide) / nAppend) + 1;
	
	A = cell(1, slide_count);
	
	if (~zpAlign)
		%% 左侧对齐
		for slide_idx = 1 : slide_count - 1
			bias = nSlide - nOverlap + (slide_idx - 2) * nAppend + 1;
			A{slide_idx} = func(x(bias : bias + nSlide - 1));
		end
		
		slide_idx = slide_idx + 1;
		bias = nSlide - nOverlap + (slide_idx - 2) * nAppend + 1;
		
		if (zPadding)
			A{slide_count} = func([x(bias : end), zeros(1, nSlide-(nData-bias+1))]);
		else
			A{slide_count} = func(x(bias : end));
		end
		
	else
		%% 右侧对齐		
		for slide_idx = slide_count : -1 : 2
			bias = nData - nSlide - (slide_count - slide_idx) * nAppend + 1;
			A{slide_idx} = func(x(bias : bias + nSlide - 1));
		end
		
		slide_idx = slide_idx - 1;
		bias = nData - nSlide - (slide_count - slide_idx) * nAppend + 1;
		bias_r = bias + nSlide - 1;
		if (zPadding)
			A{slide_count} = func([zeros(1, nSlide-bias_r), x(1 : bias_r)]);
		else
			A{slide_count} = func(x(1 : bias_r));
		end
		
	end
	
end